﻿using System;
using System.Linq;
using Crucible.Models;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Lucene.Net.Store;
using System.Collections.Generic;
using System.Data.Entity;
using System.IO;
using System.Web;

namespace Crucible.DocSearch
{
    public class IndexManager
    {
        [Flags]
        public enum SearchType
        {
            None = 0,
            Assertion = 1,
            Interpretation = 2,
            Source = 4,
            Originator = 8
        }

        //public static DocType GetDocType(NodeType nt)
        //{
        //    switch (nt)
        //    {
        //        case NodeType.Assertion:
        //        case NodeType.Interpretation:
        //        case NodeType.Source:
        //            return DocType.Linkable;
        //        case NodeType.Originator:
        //            return DocType.Originator;
        //    }

        //    return DocType.Linkable; // Default fallback
        //}

        private static Lucene.Net.Store.Directory LocalDirectory
        {
            get
            {
                var dir = new DirectoryInfo(HttpContext.Current.Server.MapPath("~/App_Data/Lucene"));
                return FSDirectory.Open(dir);
            }
        }

        private static Analyzer GetAssertionAnalyzer() { 
            return new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30); 
        }

        /// <summary>
        /// Nukes the Lucene search index and rebuilds it from the database contents.
        /// </summary>
        public static void RebuildIndex()
        {

            using (Lucene.Net.Store.Directory dir = LocalDirectory) {

                Analyzer ana = GetAssertionAnalyzer();
                using (var writer = new IndexWriter(dir, ana, true, IndexWriter.MaxFieldLength.LIMITED))
                {
                    using (var db = new ArgumentConnection())
                    {
                        foreach (Node a in db.Nodes.Include(a => a.AddedBy))
                        {
                            writer.AddDocument(AssertionToDoc(a, a.AddedBy.UserName));
                        }

                        //var sources = from s in db.Nodes
                        //              join k in db.NodeLinks on s.Id equals k.Parent_Id
                        //              join o in db.Nodes on k.Child_Id equals o.Id
                        //              select new {source = s, addedby = s.AddedBy.UserName, orgname = o.Title};



                        //foreach (var s in sources.ToList())
                        //{
                        //    writer.AddDocument(SourceToDoc(s.source, s.addedby, s.orgname));
                        //}
                    }
                }
                ana.Close();
            }
            
        }

        //public static void AddAssertion(Node a)
        //{
        //    if (a.Author == null) return;

        //    AddAssertion(a, a.Author.UserName);
        //}

        public static void AddAssertion(Node a, string author)
        {
            using (Lucene.Net.Store.Directory dir = LocalDirectory)
            {
                Analyzer ana = GetAssertionAnalyzer();
                using (var writer = new IndexWriter(dir, ana, false, IndexWriter.MaxFieldLength.LIMITED))
                {
                    writer.AddDocument(AssertionToDoc(a, author));
                }
                ana.Close();
            }
        }

        public static void EditAssertion(Node a, string author)
        {
            using (Lucene.Net.Store.Directory dir = LocalDirectory)
            {
                Analyzer ana = GetAssertionAnalyzer();
                using (var writer = new IndexWriter(dir, ana, false, IndexWriter.MaxFieldLength.LIMITED))
                {
                    DeleteAssertion(writer, a.Id.ToString());
                    writer.AddDocument(AssertionToDoc(a, author));
                }
                ana.Close();
            }
        }


        public static void DeleteAssertion(Node a)
        {
            using (Lucene.Net.Store.Directory dir = LocalDirectory)
            {
                Analyzer ana = GetAssertionAnalyzer();
                using (var writer = new IndexWriter(dir, ana, false, IndexWriter.MaxFieldLength.LIMITED))
                {
                    DeleteAssertion(writer, a.Id.ToString());
                }
                ana.Close();
            }
        }

        private static void DeleteAssertion(IndexWriter writer, string id)
        {
            var tq = new TermQuery(new Term("id", id));
            writer.DeleteDocuments(tq);
        }


        //private static Document AssertionToDoc(Node a)
        //{
        //    if (a.Author == null) return null;

        //    return AssertionToDoc(a, a.Author.UserName);
        //}

        private static Document AssertionToDoc(Node a, string author)
        {

            var doc = new Document();
            doc.Add(new Field("id", a.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED, Field.TermVector.NO));
            doc.Add(new Field("type", ((int)a.Type).ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED, Field.TermVector.NO));
            doc.Add(new Field("title", a.Title, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.NO));
            doc.Add(new Field("published", a.Published.ToString(), Field.Store.YES, Field.Index.NO, Field.TermVector.NO));
            doc.Add(new Field("author", author, Field.Store.YES, Field.Index.NOT_ANALYZED, Field.TermVector.NO));
            if (a.Body != null) {
                doc.Add(new Field("body", a.Body, Field.Store.NO, Field.Index.ANALYZED, Field.TermVector.NO));
            }
            return doc;
        }

        //private static Document SourceToDoc(Source s)
        //{
        //    if (s.AddedBy == null || s.Organization == null) return null;

        //    return SourceToDoc(s, s.AddedBy.UserName, s.Organization.Name);
        //}

        //private static Document SourceToDoc(Source s, string addedBy, string orgName)
        //{
        //    var doc = new Document();
        //    doc.Add(new Field("id", s.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED, Field.TermVector.NO));
        //    doc.Add(new Field("type", ((int)DocType.Source).ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED, Field.TermVector.NO));
        //    doc.Add(new Field("title", s.Title, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.NO));
        //    doc.Add(new Field("published", s.Published.ToString(), Field.Store.YES, Field.Index.NO, Field.TermVector.NO));
        //    doc.Add(new Field("author", addedBy, Field.Store.YES, Field.Index.NOT_ANALYZED, Field.TermVector.NO));
        //    doc.Add(new Field("org", orgName, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.NO));

        //    return doc;
        //}


        internal static List<SearchResult> SearchAssertions(string query, string userName, SearchType typeMask, int maxResults)
        {

            var results = new List<SearchResult>();

            using (Lucene.Net.Store.Directory dir = IndexManager.LocalDirectory)
            {

                Analyzer ana = GetAssertionAnalyzer();
                
                QueryParser parser = new MultiFieldQueryParser(Lucene.Net.Util.Version.LUCENE_30, new[] { "title", "body" }, ana);


                var filterQuery = new BooleanQuery {
                        new BooleanClause(new TermQuery(new Term("author", userName)), Occur.SHOULD),
                        new BooleanClause(new TermQuery(new Term("published", true.ToString())), Occur.SHOULD)
                    };

                var filter = new QueryWrapperFilter(filterQuery);

                var list = new List<string>();
                if ((typeMask & SearchType.Assertion) > 0) list.Add(((int)NodeType.Assertion).ToString());
                if ((typeMask & SearchType.Interpretation) > 0) list.Add(((int)NodeType.Interpretation).ToString());
                if ((typeMask & SearchType.Source) > 0) list.Add(((int)NodeType.Source).ToString());
                if ((typeMask & SearchType.Originator) > 0) list.Add(((int)NodeType.Originator).ToString());

                var termsFilter = new FieldCacheTermsFilter("type", list.ToArray());

                //if (Enum.IsDefined(typeof(DocType), type))
                //{
                //    filterQuery.Add(new BooleanClause(new TermQuery(new Term("type", ((int)type).ToString())), Occur.MUST));
                //}

                var chained = new ChainedFilter(new Filter[] {filter, termsFilter}, ChainedFilter.AND);

                

                using (Searcher searcher = new IndexSearcher(dir, true))
                {

                    try
                    {

                        TopScoreDocCollector collector = TopScoreDocCollector.Create(maxResults, true);

                        searcher.Search(parser.Parse(query), chained, collector);

                        foreach (ScoreDoc sd in collector.TopDocs().ScoreDocs)
                        {
                            Document doc = searcher.Doc(sd.Doc);
                            results.Add(new SearchResult() { Id = int.Parse(doc.Get("id")), Title = doc.Get("title"), Type = (NodeType)int.Parse(doc.Get("type"))});
                        }

                    }
                    catch
                    {
                        // eat
                    }
                }

                ana.Close();
            }

            return results;
        }
    }

    public class SearchResult
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public NodeType Type { get; set; }
    }

}